/*
 * ENUM Discoverer. ENUM Demonstration software, demonstrating the potential of ENUM.
 * 
 * Copyright (C) 2009 SIDN and ISOC.nl
 * 
 * This file is part of ENUM Discoverer.
 *
 * ENUM Discoverer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ENUM Discoverer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ENUM Discoverer.  If not, see <http://www.gnu.org/licenses/>.
 */

package nl.sidn.enumdiscoverer;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;

import android.database.Cursor;
import android.telephony.PhoneNumberUtils;
import android.util.Log;

/**
 * General utility methods
 * 
 * @author	Grant Patterson (grant.patterson@catalyst.net.nz)
 */
public class Utils {

	static final String TAG = LogConfig.getLogTag(Utils.class);
	// whether DEBUG level logging is enabled (whether globally, or explicitly for this log tag)
	static final boolean DEBUG = LogConfig.isDebug(TAG);
	// whether VERBOSE level logging is enabled
	static final boolean VERBOSE = LogConfig.VERBOSE;
	
	private static final int BUF_SIZE = 4096;
	private static final int INITIAL_IMAGE_BUF_SIZE = 4096;
	
	// The timeout set for HttpClient
	private static final int HTTP_TIMEOUT = 5000;  // ms
	
	private Utils() {}
	
	/**
	 * Utility method for checking that a group of parameters for a method are non-null.
	 * Checks whether all params are non-null. If so, the method returns. Otherwise an
	 * IllegalArgumentException if thrown.
	 * @param params The parameters we are asserting to be non-null
	 * @throws IllegalArgumentException if any params are null
	 */
	public static void assertParamsNotNull(Object ... params) {
		
		for (Object param : params) {
			if (param == null)
				throw new IllegalArgumentException();
		}
	}
	
	/**
	 * Builds a formatted string representation of all data referenced by a Cursor
	 * @param c A Cursor
	 * @return A string representation of the data referenced by c
	 */
	public static String cursorToTableString(Cursor c) {
		
		if (c.getColumnCount() == 0)
			return "";
		
		final int FIELD_WIDTH = 11;
		final String FIELD_FMT = "%-" + FIELD_WIDTH + "." + FIELD_WIDTH + "s";

		String fmt = FIELD_FMT + repeat("|" + FIELD_FMT, c.getColumnCount() - 1) + "\n";
		
		String result = String.format(fmt, (Object[])c.getColumnNames());
		
		String pad = repeat("-", FIELD_WIDTH);
		result += pad + repeat("+" + pad, c.getColumnCount() - 1) + "\n";
		
		int ignoreCol = c.getColumnIndex("sort_string");
		
		c.moveToPosition(-1);
		while (c.moveToNext()) {
			String[] row = new String[c.getColumnCount()];
			for (int i = 0; i < row.length; i++) {
				if (i == ignoreCol) {
					row[i] = "";
				} else {
					row[i] = c.getString(i);
				}
			}
			result += String.format(fmt, (Object[])row);
		}
		
		return result;
	}
	
	/**
	 * Copies the contents of an InputStream to an OutputStream
	 * @param in The stream to copy from
	 * @param out The stream to copy to
	 * @throws IOException If an error occurs reading from in or writing to out
	 */
	public static void copy(InputStream in, OutputStream out) throws IOException {
		
		byte[] buf = new byte[BUF_SIZE];
		
		int len;
		while ((len = in.read(buf)) != -1) {
			out.write(buf, 0, len);
		}
	}
	
	/**
	 * Closes a Closeable. Performs a check for null. IOExceptions are logged and swallowed.
	 * @param closeable The Closeable to close
	 */
	public static void close(Closeable closeable) {
		
		if (closeable != null) {
			try {
				closeable.close();
			} catch (IOException e) {
				Log.i(TAG, "Error closing " + closeable.getClass().getSimpleName(), e);
			}
		}
	}

	/**
	 * Performs a get request for a URL.
	 * @param url The URL
	 * @return A stream containing the response
	 */
	public static InputStream fetchFromUrl(String url) {
		
		HttpParams params = new BasicHttpParams();
		// time to wait for connection to be established - use HTTP_TIMEOUT
		params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT);
		// time to wait for next packet - use HTTP_TIMEOUT
		params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT);
		
		HttpClient httpclient = new DefaultHttpClient(params);
		HttpGet httpget = new HttpGet(url);
		try {
			HttpResponse response = httpclient.execute(httpget);
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return entity.getContent();
			} else {
				Log.w(TAG, "No response entity for request: " + url);
				return null;
			}
		} catch (IOException e) {
			Log.w(TAG, "Error fetching content from " + url, e);
			return null;
		}
	}
	
	/**
	 * Fetches an image from the URL. Image is returned as a byte array
	 * @param url The image URL
	 * @return A byte array containing the image
	 */
	public static byte[] fetchImageFromUrl(String url) {
		
		InputStream in = null;
		ByteArrayOutputStream out = new ByteArrayOutputStream(INITIAL_IMAGE_BUF_SIZE);
		try {
			
			in = Utils.fetchFromUrl(url);
			Utils.copy(in, out);
			
			return out.toByteArray();
			
		} catch (IOException e) {
			Log.w(TAG, "Error fetching image from " + url, e);
			return null;
		} finally {
			Utils.close(in);
			// no need to close out
		}
	}
	
	/**
	 * Ensures the number is a standard E.164 number.
	 * If number is already E.164-compliant, then it is returned unchanged.
	 * Any separator characters in the number are stripped out.
	 * If number is a local number, the local country (set in preferences) code is applied and
	 * the result returned as a E.164 number.
	 * If unable to convert to E.164 then null is returned.
	 * precondition: countryCode must have leading '+'
	 * @param number The phone number to convert to E.164
	 * @param countryCode The country code to use as a prefix to the E.164 number (including '+' character)
	 * @return An E.164 compliant phone number. null if unable to convert to E.164
	 */
	public static String phoneNumbertoE164(String number, String countryCode) {
		
		if (number == null || number.length() == 0) {
			return null;
		}
		
		PhoneNumberUtils.stripSeparators(number);
		
		// if first character is '+', assume number is already E.164
		if (number.charAt(0) == '+') {
			return number;
		}
		
		// first character should be '0'
		if (number.charAt(0) != '0') {
			return null;
		}
		// The second character is also '0' i.e string begins with '00'
		// if it has leading '00' assume it has a country code already
		else if ( number.charAt(1) == '0' ) {
			return "+" + number.substring(2);
		}
		
		// replace first character with country code (including leading '+')
		return countryCode + number.substring(1);
	}
	
	private static String repeat(String str, int repeat) {

		if (str == null) {
			return null;
		}
		if (repeat <= 0) {
			return "";
		}
		int inputLength = str.length();
		if (repeat == 1 || inputLength == 0) {
			return str;
		}

		String result = "";
		for (int i = 0; i < repeat; i++) {
			result += str;
		}
		
		return result;
	}
}
